import { GDGridScheme, BEIDOUGRID } from "../../cesiumJs/Mark/GDGrid";
import BeiDouCode from "../beidou/grid/BeiDouCode";
import { LXSPositionProperty } from "../../cesiumJs/Mark/LXSPositionPropertry";
import { Uav, UavManager } from "./uavmanager";

const DISTANCETHRESHOLD = 40;
const DISTANCESTEP = 3;
const geoc = new Cesium.Color(0, 1, 0, 0);
const outlinec = new Cesium.Color(1, 1, 0, 0);

class LXSPolyline {
    constructor(pts) {
        this._pts = pts;
        this._accumulaDists = [0];
        this._dist = 0;
        for (let i = 0; i < this._pts.length - 1; i++) {
            this._dist += Cesium.Cartesian3.distance(this._pts[i], this._pts[i + 1]);
            this._accumulaDists.push(this._dist);
        }
    }

    PointAlongLine(dist) {
        if (dist > this._dist) {
            return this._pts[this._pts.length - 1];
        }

        // find current dist align which line segment
        let curIdx = 0;
        if (dist > 0) {
            for (let i = this._accumulaDists.length - 1; i >= 0; i--) {
                if (dist > this._accumulaDists[i]) {
                    curIdx = i;
                    break;
                }
            }
        }
        else {
            return this._pts[0];
        }

        const segDist = dist - this._accumulaDists[curIdx];
        const end = new Cesium.Cartesian3();
        const t = segDist / (this._accumulaDists[curIdx + 1] - this._accumulaDists[curIdx]);
        Cesium.Cartesian3.lerp(this._pts[curIdx], this._pts[curIdx + 1], t, end);

        return end;
    }
}

class AirLine {
    constructor(id, cartesians, modelurl) {
        this._fid = id;
        this._coords = cartesians;
        this._modelurl = modelurl;
        this._dist = [0];
        this._codeList = [];

        this.calculateDist();
    }

    calculateDist() {
        for (let i = 0; i < this._coords.length - 1; i++) {
            this._dist.push(Cesium.Cartesian3.distance(this._coords[i], this._coords[i + 1]));
        }
    }

    get Fid() {
        return this._fid;
    }

    get Path() {
        return this._coords;
    }

    get ModelURL() {
        return this._modelurl;
    }

    Length(idx = 0) {
        let dist = 0;
        for (let i = 0; i <= idx; i++) {
            dist += this._dist[i];
        }
        return dist;
    }

    get CodeList() {
        return this._codeList;
    }

    Calculate(scheme, level, codemap) {
        // already calculate the beidou grid

        if (this._coords.length < 2) return;
        this._codeList.length = 0;
        const cellRes = scheme.getResolution(level);
        const deltH = BEIDOUGRID.HEIGHT[level];

        const localcodemap = new Map();
        const inses = [];
        const outlineinses = [];
        const polyline = new LXSPolyline(this._coords);
        let curdist = 0;
        while (curdist < polyline._dist) {
            const pt = polyline.PointAlongLine(curdist);
            const carto = Cesium.Cartographic.fromCartesian(pt);
            const posid = BeiDouCode.code(carto, level)._codeStr;
            if (localcodemap.has(posid)) {
                //nop
            }
            else {
                localcodemap.set(posid, 1);
                this._codeList.push(posid);
                if (codemap.has(posid)) {
                    // count + 1
                    codemap.set(posid, codemap.get(posid) + 1);
                }
            }

            // global code list

            if (!codemap.has(posid)) {
                codemap.set(posid, 1);
                const west = Math.floor(carto.longitude / cellRes.xRes) * cellRes.xRes;
                const east = west + cellRes.xRes;
                const south = Math.floor(carto.latitude / cellRes.yRes) * cellRes.yRes;
                const north = south + cellRes.yRes;
                const minh = Math.floor(carto.height / deltH) * deltH;
                const rect = Cesium.Rectangle.fromRadians(west, south, east, north);
                const rectgeom = new Cesium.RectangleGeometry({
                    ellipsoid: Cesium.Ellipsoid.default,
                    rectangle: rect,
                    height: minh,
                    extrudedHeight: minh + deltH
                });
                inses.push(new Cesium.GeometryInstance({
                    geometry: rectgeom,
                    id: posid,
                    attributes: {
                        color: new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 0.0, 0)
                    }
                }));
                const rectoutlinegeom = new Cesium.RectangleOutlineGeometry({
                    ellipsoid: Cesium.Ellipsoid.default,
                    rectangle: rect,
                    height: minh,
                    extrudedHeight: minh + deltH
                });
                outlineinses.push(new Cesium.GeometryInstance({
                    geometry: rectoutlinegeom,
                    id: posid,
                    attributes: {
                        color: new Cesium.ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 0)
                    }
                }));
            }
            curdist += DISTANCESTEP;
        }

        return { inses, outlineinses };
    }
}

class FlightLine {
    constructor(stime, etime, airidx, priority = 0) {
        this._starttime = stime;
        this._endtime = etime;
        this._airlineIdx = airidx;
        this._priority = priority;
    }

    get AirLineIdx() {
        return this._airlineIdx;
    }
    get StartTime() {
        return this._starttime;
    }
    get EndTime() {
        return this._endtime;
    }
    get Priority() {
        return this._priority;
    }
}

class FlightFactory {
    constructor(viewer) {
        this._viewer = viewer;
        this._airlines = [];
        this._flights = [];
        const ds = new Cesium.CustomDataSource("flightfactory");
        this._models = ds.entities;
        this._viewer.dataSources.add(ds);
        this._scheme = new GDGridScheme();
        this._level = 7;
        this._viewgrid = false;
        this._codeMap = new Map();
        this._collisionMap = new Map();
        this._collision = false;
        this._pausedUavMap = new Map();

        this._gridPri = null;
        this._gridOutLinePri = null;

        this._um = new UavManager();

        this._viewer.clock.onTick.addEventListener(FlightFactory.prototype.tickUpdate, this);
    }

    tickCallBack(cb) {
        this._tickCallBack = cb;
    }
    collisionCallBack(cb) {
        this._collisionCB = cb;
    }

    /**
     * 
     * @param {AirLine} line 
     */
    AddAirLine(line) {
        this._airlines.push(line);
    }

    /**
     * 
     * @param {FlightLine} f 
     * @param {Number} alidx index of airline
     */
    AddFlight(f) {
        this._flights.push(f);
    }

    Clear() {
        this._models.removeAll();
        this._viewer.scene.primitives.remove(this._gridPri);
        this._viewer.scene.primitives.remove(this._gridOutLinePri);
        this.Stop();
        this._airlines.length = 0;
        this._flights.length = 0;
        this._codeMap.clear();
        this._collisionMap.clear();
        this._collision = false;
        this._pausedUavMap.clear();
        this._uavs = [];
        this._um.Clear();
        this._status = 0;
    }

    Prepare() {
        if (this._airlines.length === 0 || this._flights.length === 0) return;
        this._models.removeAll();
        this._uavs.length = 0;
        // this method also calculate time interval
        this.drawAirLines();
        const inses = [];
        const outlineinses = [];
        const codemap = new Map();
        for (const al of this._airlines) {
            const pinses = al.Calculate(this._scheme, this._level, codemap);
            inses.push(...pinses.inses);
            outlineinses.push(...pinses.outlineinses);
        }

        for (const k of codemap.keys()) {
            if (codemap.get(k) > 1) {
                this._um.AddGrid(k, null);
            }
        }

        this._gridPri = new Cesium.Primitive({
            geometryInstances: inses,
            appearance: new Cesium.PerInstanceColorAppearance({ flat: true }),
            allowPicking: false
        });
        this._gridOutLinePri = new Cesium.Primitive({
            geometryInstances: outlineinses,
            appearance: new Cesium.PerInstanceColorAppearance({ flat: true }),
            allowPicking: false
        });

        this._viewer.scene.primitives.add(this._gridPri);
        this._viewer.scene.primitives.add(this._gridOutLinePri);

        this._status = 1;
    }

    Start() {
        this._collision = false;
        this._viewer.clock.shouldAnimate = true;
    }

    Stop() {
        this._viewer.clock.shouldAnimate = false;
    }

    ViewGrid(vis) {
        this._viewgrid = vis;
        this._gridPri.show = this._gridOutLinePri.show = vis;
    }

    tickUpdate(clock) {
        if (this._status == 0) return;
        const time = clock.currentTime;
        if (!!this._tickCallBack) {
            this._tickCallBack();
        }
        if (this._collision) return;
        this._codeMap.clear();
        this._collisionMap.clear();
        // each model only view current and next grid
        for (const u of this._uavs) {
            const en = u._en;
            const fid = en.AirLineIdx;
            const al = this._airlines.filter(a => a.Fid === fid)[0];
            const mpos = en.position.getValue(time);
            if (!mpos) continue;
            const carto = Cesium.Cartographic.fromCartesian(mpos);
            const posid = BeiDouCode.code(carto, this._level)._codeStr;
            u.Grid(posid);
            this._codeMap.set(posid, 1);
            const gridIdx = al.CodeList.indexOf(posid);
            if (gridIdx < al.CodeList.length - 1 && gridIdx > -1) {
                // here we check is the next grid of uav is same
                // so when this uav walk through two grid, the other is safe
                this._codeMap.set(al.CodeList[gridIdx + 1], 1);
                u.NextGrid(al.CodeList[gridIdx + 1]);
                const cproxy = this._collisionMap.get(al.CodeList[gridIdx + 1])
                if (!!cproxy) {
                    cproxy.count += 1;
                    cproxy.uavs.push(u);
                }
                else {
                    this._collisionMap.set(al.CodeList[gridIdx + 1], { uavs: [u], count: 1 });
                }
            }
        }

        this.HighLightGrids(this._codeMap,
            Cesium.Color.GREEN.withAlpha(0.3),
            Cesium.Color.YELLOW);

        // check if there have collision, set red for collision grid
        for (const key of this._collisionMap.keys()) {
            const cproxy = this._collisionMap.get(key);
            if (cproxy.count > 1) {
                cproxy.uavs.forEach(u => {
                    // shouldn't add each tick
                    this._um.AddGrid(key, u);
                });
            }
        }
        this._um.Refresh();

        for(const u of this._uavs){
            if(u._en.position._pause){
                setInsColor(this._gridPri,u._posid,Cesium.Color.ORANGE.withAlpha(0.3));
                setInsColor(this._gridPri,u._posidNext,Cesium.Color.ORANGE.withAlpha(0.3));
            }
        }
    }

    drawAirLines() {
        let totalstart = new Date("2991-01-01");
        let totalend = new Date("1991-01-01");
        const self = this;
        // get all flights duration
        for (const al of this._airlines) {
            const fid = al.Fid;
            const fls = this._flights.filter(f => f.AirLineIdx === fid)
            if (fls.length === 0) continue;
            const tic = [];
            for (const fl of fls) {
                if (totalstart > fl.StartTime) totalstart = fl.StartTime;
                if (totalend < fl.EndTime) totalend = fl.EndTime;

                const s = Cesium.JulianDate.fromDate(fl.StartTime);
                const e = Cesium.JulianDate.fromDate(fl.EndTime);

                tic.push(new Cesium.TimeInterval({ start: s.clone(), stop: e.clone() }));

                // add model for per flight
                const secodespan = Cesium.JulianDate.secondsDifference(e, s);
                const availability1 = new Cesium.TimeIntervalCollection(
                    [new Cesium.TimeInterval({ start: s.clone(), stop: e.clone() })]
                );
                const property = new LXSPositionProperty();
                property.name = fl.AirLineIdx;
                const totaldist = al.Length(al.Path.length - 1);
                for (let i = 0; i < al.Path.length; i++) {
                    if (i === 0) {
                        property.addSample(s.clone(), al.Path[i])
                    } else {
                        const curdist = al.Length(i);
                        const sec = secodespan * curdist / totaldist;
                        const t = Cesium.JulianDate.addSeconds(s, sec, new Cesium.JulianDate());
                        property.addSample(t, al.Path[i]);
                    }
                }
                const velocityOrientationProperty = new Cesium.VelocityOrientationProperty(property)
                const uaven = this._models.add({
                    // availability: availability1,
                    model: { uri: al.ModelURL },
                    position: property,
                    orientation: velocityOrientationProperty,
                    label: {
                        text: new Cesium.CallbackProperty(_ => {
                            // update beidou code
                            const pos = property.getValue(self._viewer.clock.currentTime);
                            if (!pos) return "";
                            const carto = Cesium.Cartographic.fromCartesian(pos);
                            const posid = BeiDouCode.code(carto, this._level)._codeStr;
                            return posid;
                        }, false),
                        scale: 0.5,
                        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                        pixelOffset: new Cesium.Cartesian2(0, -20),
                        // scaleByDistance:new Cesium.NearFarScalar()
                    }
                });
                uaven.AirLineIdx = al.Fid;
                uaven.priority = fl.Priority;
                const u = new Uav(uaven);
                this._uavs.push(u);
            }

            const availability2 = new Cesium.TimeIntervalCollection(tic);
            this._models.add({
                // availability: availability2,
                name: al.Fid,
                polyline: {
                    positions: al.Path,
                    width: 10,
                    material: new Cesium.PolylineGlowMaterialProperty({
                        color: Cesium.Color.fromCssColorString('rgba(0,255,0,1)'),
                        glowPower: 0.25
                    }),
                },
                show: true
            });
        }

        // set clock
        this._viewer.clock.startTime = Cesium.JulianDate.fromDate(totalstart);
        this._viewer.clock.stopTime = Cesium.JulianDate.fromDate(totalend);
        this._viewer.clock.currentTime = Cesium.JulianDate.fromDate(totalstart);
        this._viewer.clock.multiplier = 1.0;
    }

    gridValid() {
        const geom = !!this._gridPri && this._gridPri.show;
        const outline = !!this._gridOutLinePri && this._gridOutLinePri.show;
        return geom && outline;
    }

    HighLightGrids(posidmap, pgeoc, poutlinec) {
        if (!this.gridValid()) return;
        const ids = this._gridPri._instanceIds;
        for (const id of ids) {
            if (posidmap.get(id)) {
                setInsColor(this._gridPri, id, pgeoc);
                setInsColor(this._gridOutLinePri, id, poutlinec);
            }
            else {
                setInsColor(this._gridPri, id, geoc);
                setInsColor(this._gridOutLinePri, id, outlinec);
            }
        }
    }
}

function setInsColor(pri, posid, color) {
    if (!pri.ready) return;

    const attributes = pri.getGeometryInstanceAttributes(posid);
    if (!!attributes) {
        attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(color);
    }
}


export { AirLine, FlightLine, FlightFactory }